<template>
    <div class="containerGraph" id='graphContainer'>
        <!-- 自定义节点面板 -->
        <ActionBar ref="actionBar" />
        <!-- 画布 -->
        <div id="graphNode"></div>
        <!-- 修改面板 -->
        <Configure @change="changeNode" :node="state.graphNodeModel" @save-json="saveJsonInfo"
            @export-image-download="exportImageDownload" :type="state.type" />
        <!-- 添加文字 -->
        <el-dialog v-model="addText.visible" title="添加文字" width="30%">
            <template #footer>
                <span style="display:flex;justify-content: space-around;">
                    <el-button @click="(addText.visible = false)">取消</el-button>
                    <el-button type="primary" @click="saveText">确认</el-button>
                </span>
            </template>
            <el-input v-model="addText.text"></el-input>
        </el-dialog>
        <!-- 添加自定义数据 -->
        <el-dialog v-model="customForm.visible" title="添加自定义数据" width="40%">
            <template #footer>
                <span style="display:flex;justify-content: space-around;">
                    <el-button @click="(customForm.visible = false)">取消</el-button>
                    <el-button @click="addCustomInfo">添加一行</el-button>
                    <el-button type="primary" @click="saveCustomInfo">确认</el-button>
                </span>
            </template>
            <el-form label-width="0px">
                <el-form-item v-for="(item, index) in customForm.domain">
                    <div class="item">
                        <span class="label">key:</span>
                        <el-input v-model="item.key"></el-input>
                        <span class="label">value:</span>
                        <el-input v-model="item.value"></el-input>
                        <el-button style="margin-left: 10px;" type="danger" @click="removeCustomItem(index)">删除
                        </el-button>
                    </div>
                </el-form-item>
            </el-form>
        </el-dialog>
        <!-- 查看自定义数据 -->
        <el-dialog v-model="customTable.visible">
            <el-table :data="customTable.tableData" style="width: 100%">
                <el-table-column prop="key" label="key" />
                <el-table-column prop="value" label="value" />
            </el-table>
        </el-dialog>
        <!-- 保存节点 -->
        <el-dialog v-model="customCombo.visible" title="节点名称">
            <template #footer>
                <div>
                    <el-button size="large" @click="(customCombo.visible = false)">取消</el-button>
                    <el-button size="large" type="primary" @click="saveCustomCombo">确认</el-button>
                </div>
            </template>
            <div style="display:flex;align-items:center">
                <label style="width:100px;text-align:center;font-size:16px;">名称</label><el-input
                    v-model="customCombo.name"></el-input>
            </div>
        </el-dialog>
    </div>
</template>

<script lang='ts' setup>
import G6 from "@antv/g6";
import {onMounted, reactive, ref } from 'vue';
import { getNodeModel, registerNode, updateShapePosition } from '@/utils/registerGraph';
import ActionBar from './actionBar/index.vue';
import Configure from './configure/index.vue';
import { ElMessage } from 'element-plus';
import { IState } from '@/types/graph';
import { useGraph } from "@/pinia";
const GraphStore = useGraph()
const state = reactive<IState>({
    graph: null,
    graphNode: null,
    graphNodeModel: {},
    sourceAnchorIdx: null,
    targetAnchorIdx: null,
    type: "",
})
const customCombo = reactive({
    visible: false,
    name: '',
    value: {}
})
const defaultData = () => {
    return {
    }
}
const defaultConfig = () => {
    const grid = new G6.Grid()

    const menu = defaultMenu()

    const map = new G6.Minimap({
        size: [200, 100],
        className:"minimap",
        container:"graphContainer"
    })

    const config = {
        container: 'graphNode',
        width: document.getElementById("graphContainer")?.clientWidth,
        height: document.getElementById("graphContainer")?.clientHeight,
        animate: true,
        animateCfg: {
            duration: 500,
            easing: 'easeLinear',
        },
        groupByTypes: false,

        layout: {
            type: 'dagre',

            nodesep: 30,
            ranksep: 30,
        },

        modes: {

            default: [
                'drag-canvas',
                'zoom-canvas',
                {
                    type: 'drag-node',

                    enableDelegate: false,
                    updateEdge: true,


                    shouldBegin: (e: any) => {

                        if (e.target.get('name') === 'node-shape') return false;
                        return true;
                    }
                },
                {

                    type: 'create-edge',
                    trigger: "drag",

                    shouldBegin: (e: any) => {

                        if (e.target && e.target.get('name') !== 'node-shape') return false

                        state.sourceAnchorIdx = e.target.get('anchorPointIdx')

                        e.target.set('links', e.target.get('links') + 1)
                        return true;
                    },
                    shouldEnd: (e: any) => {

                        if (e.target && e.target.get('name') !== 'node-shape') return false

                        if (e.target) {

                            state.targetAnchorIdx = e.target.get('anchorPointIdx')
                            e.target.set('links', e.target.get('links') + 1)
                            return true;
                        }
                        state.targetAnchorIdx = null;
                        return true;
                    },
                },
                {
                    type: 'drag-combo',
                    enableDelegate: false,
                }
            ],
        },

        defaultNode: {
            type: 'rect',
            size: [50, 50],
            style: {
                radius: 10,
                fill: "#3081b3",
                stroke: "#049eff",
                lineWidth: 2,
                cursor: "pointer"
            },
            label: "",
            labelCfg: {
                style: {
                    fill: "#ffffff",
                    fontSize: 12
                }
            }
        },

        defaultEdge: {

            type: 'quadratic',
            label: "",
            labelCfg: {
                refX: 0,
                refY: 0,
                style: {
                    fill: "#000000",
                    fontSize: 12
                }
            },
            style: {
                stroke: '#F6BD16',
                lineWidth: 2,
                endArrow: true,
                lineAppendWidth: 15,
                cursor: "pointer"
            },
        },

        defaultCombo: {
            label: "",
            labelCfg: {
                refX: 0,
                refY: 0,
                style: {
                    fill: "#000",
                    fontSize: 12
                }
            },
            style: {
                fill: "#87CEFA",
                stroke: "#87CEFA",
                lineWidth: 1,
                fillOpacity: 0.6,
                cursor: 'move'
            }
        },

        plugins: [grid, menu, map],
    }
    
    return config
}
const defaultMenu = () => {
    return new G6.Menu({
        offsetX: -20,
        offsetY: -50,
        itemTypes: ['node', 'edge', 'combo'],
        getContent(evt: any) {
            let type = evt.item.getType()
            const outDiv = document.createElement('div');
            outDiv.style.width = '100px';
            outDiv.style.cursor = 'pointer';
            outDiv.innerHTML = `
            <p class="menuNode" id="delete">删除节点</p>
            ${['node'].includes(type) ? ' <p class="menuNode" id="copy">复制节点</p>' : ''}
            ${['node', 'edge'].includes(type) ? ' <p class="menuNode" id="add">自定义数据</p><p class="menuNode" id="see">查看数据</p>' : ''}
            ${type === 'combo' ? '<p class="menuNode" id="saveGroup">保存分组</p>' : ''}
                                        `
            return outDiv;
        },

        handleMenuClick(target: any, item: any) {
            const { id } = target;

            if (id === "delete") {

                state.graph.removeItem(item)
            }

            if (id === "copy") {
                const { label, style, labelCfg, x, y, size, type } = state.graphNodeModel
                const model = {
                    id: 'node:' + Math.random(),
                    label,
                    x: typeof size === 'number' ? x + size : x + size[0] + 10,
                    y,
                    style,
                    labelCfg,
                    type,
                    size,
                };

                state.graph.addItem('node', model);
            }

            if (id === 'add') {
                customForm.domain = item.getModel().custom ? JSON.parse(JSON.stringify(item.getModel().custom)) : []
                customForm.visible = true
            }

            if (id === 'see') {
                customTable.tableData = item.getModel().custom
                customTable.visible = true
            }

            if (id === 'saveGroup') {
                let combo = JSON.parse(JSON.stringify(item.getModel()))
                let nodes = getNodeModel(item)
                delete combo.id
                customCombo.value = {
                    combo: combo,
                    nodes: nodes,
                }
                customCombo.visible = true
            }
        },
    })
}
const eventList = (graph: any) => {

    let flag = 0

    graph.on('drop', (event: any) => {

        if (flag > 0) return flag = 0

        const { dataTransfer } = event.originalEvent
        if (!dataTransfer) return
        const t = event.item?.getModel().type
        const node = JSON.parse(dataTransfer.getData('nodeType'))
        const { x, y } = event
        if (node.type === 'circle' || node.type === 'rect') {

            let options: any = {
                id: `combo-${Math.random()}`,
                type: node.type,
                x,
                y
            }
            if (t === 'rect' || t === 'circle') {
                flag++
                options.parentId = event.item.getModel().id
            }
            if (t && t !== 'rect' && t !== 'circle') {
                options.nodes = true
            }

            graph.createCombo(options, [])
            options.parentId ? flag++ : flag = 0
            options.nodes ? flag++ : ''
            return
        }
        if (node.type === 'custom') {

            let info = node.data
            let comboid = 'combo-' + Math.random()
            state.graph.createCombo({ ...info.combo, id: comboid }, [])
            info.nodes.forEach((n: any, idx: number) => {
                let id = 'node-' + Math.random()
                graph.addItem('node', {
                    ...n,
                    x: x - n.position.x,
                    y: y - n.position.y,
                    id,
                    comboId: comboid
                })
            })
            flag = 0
            return
        }
        let id = 'node-' + Math.random()

        let option: any = {
            id, type: node.type, x, y, style: node.style, size: node.size
        }
        if (t === 'rect' || t === 'circle') {
            option.comboId = event.item.getModel().id
            flag++
        }
        graph.addItem('node', option)

        option.comboId ? '' : flag = 0
    })

    graph.on('node:mousemove', (e: any) => {
        graph.setItemState(e.item, 'showAnchors', true);
    })

    graph.on('node:mouseout', (e: any) => {
        graph.setItemState(e.item, 'showAnchors', false);
    })
    graph.on('node:click', (e: any) => {
        const model = e.item.getModel()

        state.graphNodeModel = model
        state.graphNode = e.item
    })
    graph.on('node:dragenter', (e: any) => {
        graph.setItemState(e.item, 'showAnchors', true);
    })
    graph.on('node:dragleave', (e: any) => {
        graph.setItemState(e.item, 'showAnchors', false);
    })
    graph.on('node:dragstart', (e: any) => {
        graph.setItemState(e.item, 'showAnchors', true);
    })
    graph.on('node:dragout', (e: any) => {
        graph.setItemState(e.item, 'showAnchors', false);
    })

    graph.on('afteradditem', (e: any) => {
        if (e.item && e.item.getType() === 'edge') {
            graph.updateItem(e.item, {
                sourceAnchor: state.sourceAnchorIdx
            });
        }
        if (e.item && e.item.getType() === 'node') {
            const model = e.item.getModel()
            state.graphNodeModel = model
            state.graphNode = e.item
        }
    })

    graph.on('afterupdateitem', (e: any) => {
        const group = e.item.getContainer()
        const shape = group.get('children')
        const model = e.item.getModel()
        const box = group.getBBox()
        let i = 0
        shape.forEach((item: any, index: number) => {
            if (!item.cfg.className) {
                item.attr(updateShapePosition(model, box, i))
                i++
            }
        })
    })
    /**
     * @边的相关事件
     */
    graph.on('aftercreateedge', (e: any) => {

        graph.updateItem(e.edge, {
            sourceAnchor: state.sourceAnchorIdx,
            targetAnchor: state.targetAnchorIdx
        })
        const edges = graph.save().edges;
        G6.Util.processParallelEdges(edges)
        graph.getEdges().forEach((edge: any, i: any) => {
            graph.updateItem(edge, {
                curveOffset: edges[i].curveOffset,
                curvePosition: edges[i].curvePosition,
            });
        })
    })
    graph.on("edge:click", (e: any) => {
        state.graphNodeModel = e.item.getModel()
        state.graphNode = e.item
    })
    graph.on('edge:mousemove', function (e: any) {
        e.item.setState('selected', true)
    })
    graph.on('edge:mouseout', function (e: any) {
        e.item.setState('selected', false)
    })

    /**
     * @combo的相关事件
     */
    graph.on('combo:click', (e: any) => {
        state.graphNode = e.item
        state.graphNodeModel = e.item.getModel()
    })
    graph.on('click', (e: any) => {
        state.type = e.item?.getType()
    })
}
let icons = [
    {
        type: "circle-node"
    },
    {
        type: "star-node",
    },
    {
        type: "rect-node",
    },
    {
        type: "triangle-node",
    },
]
for (let i = 0; i < icons.length; i++) {
    const item = icons[i];
    // 注册自定义节点
    registerNode(item.type, item.type.split('-')[0])
}
onMounted(() => {
    state.graph = new G6.Graph(defaultConfig())
    state.graph.data(defaultData())
    state.graph.render()
    eventList(state.graph)
})
const changeNode = (node: any) => {
    if (!state.graphNode) return
    if (node.type === 'text-add') {
        addText.visible = true
        return
    }
    state.graph.updateItem(state.graphNode, node)
    state.graphNodeModel = state.graphNode.getModel()
}
let addText = reactive({
    visible: false,
    text: ''
})
const saveText = () => {
    if (addText.text === '') return
    state.graph.updateItem(state.graphNode, {
        label: addText.text
    })
    addText.visible = false
    addText.text = ''
}
const customForm: any = reactive({
    domain: [
        {
            key: "",
            value: ""
        }
    ],
    visible: false
})
const addCustomInfo = () => {
    customForm.domain.push({ key: "key", value: "value" })
}
const removeCustomItem = (i: number) => {
    customForm.domain.splice(i, 1)
}
const saveCustomInfo = () => {
    state.graph.updateItem(state.graphNode, {
        custom: customForm.domain
    })
    customForm.visible = false
}
// 保存自定义分组到本地
const saveCustomCombo = async () => {

    if (customCombo.name === "") return ElMessage.error("请输入名称")
    let obj = {
        id: "",
        name: customCombo.name,
        data: JSON.stringify(customCombo.value)
     }
    GraphStore.addGroupList(obj)
    /**
     * 调接口存储到数据库中
     let obj = {
        id: "",
        name: customCombo.name,
        data: JSON.stringify(customCombo.value)
     }
     const { data: { code, msg } } = await createCustomG6List(obj)

     if (code !== 200) return ElMessage.error(msg)
    */

    ElMessage.success('添加成功')
    customCombo.value = ""
    customCombo.visible = false
    customCombo.name = ""
    
    actionBar.value.getCombos()
}
const customTable: any = reactive({
    visible: false,
    tableData: []
})
const saveJsonInfo = () => {
    ElMessage.success("请在控制台查看数据!")
    console.log("获取JSON数据",state.graph.save())
}
const exportImageDownload = () => {
    state.graph.downloadFullImage('graph', 'image/png')
}
let actionBar = ref()
</script>

<style lang="less" scoped>
.containerGraph {
    width: 100%;
    height: 100%;
    position: relative;
    overflow: hidden;
}

.g6-minimap {
    position: absolute;
    right: 20px;
    bottom: 20px;
    box-shadow: 0px 0px 13px 0px #ccc;
    background-color: #fff;
    z-index: 9;

    canvas {
        position: static !important;
        z-index: 9 !important;
    }
}

.g6-grid-container {
    z-index: 0 !important;

}

#graphNode {
    height: 90%;
    width: 100%;
    canvas {
        position: absolute;
        z-index: 1;
    }
}

:deep(.g6-component-contextmenu) {
    z-index: 9;
    padding: 0px !important;

    .menuNode {
        margin: 0px;
        padding: 5px 10px;
        border-radius: 4px;

        &:hover {
            background-color: #049eff !important;
            color: #fff;
        }
    }

    #delete {
        &:hover {
            background-color: #f56c6c !important;
            color: #fff;
        }
    }
}

.tooltip {
    position: fixed;
    z-index: 999;
    background-color: #000;
}

.item {
    width: 100%;
    display: flex;
    align-items: center;

    .label {
        width: 50px;
        min-width: 50px;
        text-align: center;
    }
}
:deep(.minimap) {
    position: absolute;
    right: 75px;
    bottom: 15px;
    background-color: #fff;
    box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;
}
</style>